package cluster

import (
	"context"
	"sync"

	"gddgame.cc/galaxy/asteroid"

	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

type Engine interface {
	SetLogger(logger def.Logger)
	AttachContext(ctx context.Context)
	Register(identity string, region *asteroid.Region) *container
	Container(identity string) *container
	On(event string, f interface{}) error
	Exit() error
	Close() error

	RegisterHash(service HashService)
	RegisterBalance(service BalanceService)
}

type engine struct {
	def.Logger

	containers map[string]*container

	event utils.Event
	ctx   context.Context
	mutex sync.RWMutex
}

var (
	resultPool = &sync.Pool{}
	errPool    = &sync.Pool{}
)

func init() {
	resultPool.New = func() interface{} {
		return make(chan interface{})
	}
	errPool.New = func() interface{} {
		return make(chan error)
	}
}

func NewEngine() Engine {
	e := &engine{
		containers: map[string]*container{},
		event:      utils.NewEvent(),
		ctx:        context.Background(),
		mutex:      sync.RWMutex{},
	}
	return e
}

func (e *engine) SetLogger(logger def.Logger) {
	e.Logger = logger
}

func (e *engine) Register(identity string, region *asteroid.Region) *container {
	e.mutex.Lock()
	a := newContainer(e, region, identity)
	e.containers[identity] = a
	e.mutex.Unlock()
	return a
}

func (e *engine) Container(identity string) *container {
	e.mutex.RLock()
	i, ok := e.containers[identity]
	e.mutex.RUnlock()
	if !ok {
		return nil
	}
	return i
}

func (e *engine) On(event string, f interface{}) error {
	return e.event.On(event, f)
}

func (e *engine) RegisterHash(service HashService) {
	registerHash(service)
}

func (e *engine) RegisterBalance(service BalanceService) {
	registerBalance(service)
}

func (e *engine) AttachContext(ctx context.Context) {
	e.ctx = ctx
}

func (e *engine) Exit() error {
	e.Debugf("[ Cluster ] Start exit")
	ids := make([]string, 0)
	exitChan := make(chan string)
	e.mutex.RLock()
	for _, container := range e.containers {
		ok := container.Exit(exitChan)
		if ok {
			ids = append(ids, container.ID())
		}
	}
	e.mutex.RUnlock()
	//e.containers.Range(func(key, value interface{}) bool {
	//	value.(*container).exit = true
	//	return true
	//})
	if len(ids) < len(e.containers) {
		for id := range exitChan {
			e.Debugf("[ Cluster ] Container %s exit success", id)
			ids = append(ids, id)
			if len(ids) == len(e.containers) {
				break
			}
		}
	}
	e.Infof("[ Cluster ] Finish exit")
	return nil
}

func (e *engine) Close() error {
	e.mutex.RLock()
	for _, container := range e.containers {
		container.close()
	}
	e.mutex.RUnlock()
	//e.containers.Range(func(key, value interface{}) bool {
	//	value.(*container).close()
	//	return true
	//})
	return nil
}
